Clinical Data Journey (Phase 2): Metadata-Assisted SDTM DM Programming in R using sdtm.oak

Objective

In the previous article, we transformed raw demographics data into an SDTM DM domain using manual R programming.

While manual programming is useful for understanding SDTM concepts, clinical programming workflows often use metadata specifications to improve consistency, traceability, and maintainability.

In this article, we will introduce a metadata-assisted approach where SDTM variable definitions are stored separately from programming logic. We will also explore how sdtm.oak utilities can support SDTM development.

Load Required Packages

library(tidyverse)
library(lubridate)
library(sdtm.oak)

Import the Raw Data

raw_dm <- read_csv("RAW_Demo.csv",show_col_types = FALSE)

Review the Raw Data

head(raw_dm)
# A tibble: 6 × 15
  STUDYID   SITEID  Patient_ID   Age Gender RACE   ETHNICITY HEIGHT_CM WEIGHT_KG
  <chr>     <chr>   <chr>      <dbl> <chr>  <chr>  <chr>         <dbl>     <dbl>
1 ONCO-2026 SITE004 SUBJ0001      62 Male   WHITE  NOT HISP…      167.      78.2
2 ONCO-2026 SITE014 SUBJ0002      84 Female BLACK… NOT HISP…      164.     100. 
3 ONCO-2026 SITE010 SUBJ0003      71 Female WHITE  NOT HISP…      168       79.6
4 ONCO-2026 SITE007 SUBJ0004      49 Female ASIAN  NOT HISP…      174       79.8
5 ONCO-2026 SITE002 SUBJ0005      45 Female ASIAN  NOT HISP…      154.      73.2
6 ONCO-2026 SITE002 SUBJ0006      64 Female BLACK… NOT HISP…      145       92.4
# ℹ 6 more variables: BMI <dbl>, BIRTH_DATE <chr>, COUNTRY <chr>,
#   RAND_DATE <chr>, ARM <chr>, TREATMENT_START_DATE <chr>

Import Metadata Specification

dm_metadata <- read_csv("DM_Metadata.csv",show_col_types = FALSE)

Review the Metadata

head(dm_metadata)
# A tibble: 6 × 7
  SDTM_VAR RAW_VAR                   LABEL             TYPE  LENGTH ORIGIN ROLE 
  <chr>    <chr>                     <chr>             <chr>  <dbl> <chr>  <chr>
1 STUDYID  STUDYID                   Study Identifier  char…     20 CRF    Iden…
2 DOMAIN   <NA>                      Domain Abbreviat… char…      2 Assig… Iden…
3 USUBJID  STUDYID+SITEID+Patient_ID Unique Subject I… char…     40 Deriv… Iden…
4 SUBJID   Patient_ID                Subject Identifi… char…     20 CRF    Iden…
5 SITEID   SITEID                    Study Site Ident… char…     10 CRF    Iden…
6 AGE      Age                       Age               nume…      8 CRF    Topic

The metadata file acts as a specification layer between raw data and the final SDTM dataset. It documents SDTM variables, source variables, labels, data types, and derivation information.

Instead of embedding dataset specifications directly in the R script, metadata stores them separately, making the workflow easier to understand, maintain, and reuse.

Use Metadata to Define SDTM Structure

Extract SDTM variables:

sdtm_vars <- dm_metadata$SDTM_VAR

sdtm_vars
 [1] "STUDYID"  "DOMAIN"   "USUBJID"  "SUBJID"   "SITEID"   "AGE"     
 [7] "SEX"      "RACE"     "ETHNIC"   "BRTHDTC"  "COUNTRY"  "ARMCD"   
[13] "ARM"      "RFXSTDTC"

The metadata now defines the expected SDTM variable structure, reducing the need to hard-code variable names in the R script.

Perform SDTM derivations

DM <- raw_dm %>%

mutate(

DOMAIN = "DM",

USUBJID = paste(
STUDYID,
SITEID,
Patient_ID,
sep="-"
),

SUBJID = Patient_ID,

SEX = case_when(
Gender=="Male" ~ "M",
Gender=="Female" ~ "F",
TRUE ~ NA_character_
),

AGE = Age,

ETHNIC = ETHNICITY,

ARMCD = case_when(
ARM=="ARM A" ~ "A",
ARM=="ARM B" ~ "B",
TRUE ~ NA_character_
),

BRTHDTC =
format(
dmy(BIRTH_DATE),
"%Y-%m-%d"
),

RFXSTDTC =
format(
dmy(TREATMENT_START_DATE),
"%Y-%m-%d"
)

)

Apply Metadata-Defined Structure

Instead of manually selecting variables:

DM <- DM %>%
select(all_of(sdtm_vars))

The metadata now controls the final SDTM variable structure.

Introducing sdtm.oak

Add OAK Traceability Identifiers

sdtm.oak provides reusable functions that support common SDTM programming activities.

One useful utility function is generate_oak_id_vars(), which adds standardized traceability variables to the raw dataset.

Generate OAK identifiers

DM_oak <- generate_oak_id_vars(
  raw_dat = raw_dm,
  pat_var = "Patient_ID",
  raw_src = "RAW_Demo"
)

names(DM_oak)
 [1] "oak_id"               "raw_source"           "patient_number"      
 [4] "STUDYID"              "SITEID"               "Patient_ID"          
 [7] "Age"                  "Gender"               "RACE"                
[10] "ETHNICITY"            "HEIGHT_CM"            "WEIGHT_KG"           
[13] "BMI"                  "BIRTH_DATE"           "COUNTRY"             
[16] "RAND_DATE"            "ARM"                  "TREATMENT_START_DATE"

The function adds three traceability variables:

  • oak_id: A unique identifier generated by OAK for traceability.

  • raw_source: Captures the origin of the raw dataset.

  • patient_number: Provides a standardized patient-level identifier.

These variables support reproducibility and data lineage during SDTM development.

Validation

Check that all metadata-defined variables are present

The metadata specification defines the expected SDTM DM variables. This check verifies that every variable listed in the metadata exists in the final SDTM dataset.

setdiff(
dm_metadata$SDTM_VAR,
names(DM)
)
character(0)
Check for one record per subject

The SDTM DM domain should contain one record for each unique subject. This check verifies that no duplicate USUBJID values exist.

DM %>%
count(USUBJID) %>%
filter(n > 1)
# A tibble: 0 × 2
# ℹ 2 variables: USUBJID <chr>, n <int>

No duplicate USUBJID values were found, confirming that the dataset contains one record per subject.

Compare Phase 1 and Phase 2

Phase 1 Phase 2
Variables defined inside R code Variable structure stored in metadata
Manual variable selection Metadata-controlled variable selection
Programming logic only Metadata + programming logic
No traceability layer OAK traceability variables

Conclusion

In this phase, we introduced a metadata-assisted approach to SDTM programming by separating dataset specifications from transformation logic. We also explored how sdtm.oak can improve traceability through standardized identifier utilities. While study-specific derivations still require programming logic, this approach improves organization and lays the foundation for more advanced metadata-driven workflows.

In the next phase, we’ll introduce the metacore package and learn how structured SDTM specification workbooks can be converted into metadata objects, providing the foundation for standardized and scalable SDTM programming.